// T3DLIB1.CPP - Game Engine Part I

// INCLUDES ///////////////////////////////////////////////

#define WIN32_LEAN_AND_MEAN  

// has the GUID library been included?
//#ifndef INITGUID
//#define INITGUID
//#endif

#define DEBUG_ON

#include <windows.h>   // include important windows stuff
#include <windowsx.h> 
#include <mmsystem.h>
#include <iostream> // include important C/C++ stuff
#include <conio.h>
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <math.h>
#include <io.h>
#include <fcntl.h>
#include <sys/timeb.h>
#include <time.h>
#include "MathDefine.h"

// DEFINES ////////////////////////////////////////////////

// TYPES //////////////////////////////////////////////////

// PROTOTYPES /////////////////////////////////////////////

// EXTERNALS /////////////////////////////////////////////

extern HWND main_window_handle; // save the window handle
extern HINSTANCE main_instance; // save the instance

// GLOBALS ////////////////////////////////////////////////

FILE *fp_error                    = NULL; // general error file
char error_filename[80];                  // error file name

// storage for our lookup tables
float cos_look[361]; // 1 extra element so we can store 0-360 inclusive
float sin_look[361];
// FUNCTIONS //////////////////////////////////////////////
#if 0 
inline void Mem_Set_WORD(void *dest, USHORT data, int count)
{
	// this function fills or sets unsigned 16-bit aligned memory
	// count is number of words

	//Write_Error("{");

	_asm 
	{ 
		mov edi, dest   ; edi points to destination memory
			mov ecx, count  ; number of 16-bit words to move
			mov ax,  data   ; 16-bit data
			rep stosw       ; move data
	} // end asm

	//Write_Error("}");

} // end Mem_Set_WORD

///////////////////////////////////////////////////////////

inline void Mem_Set_QUAD(void *dest, UINT data, int count)
{
	// this function fills or sets unsigned 32-bit aligned memory
	// count is number of quads

	_asm 
	{ 
		mov edi, dest   ; edi points to destination memory
			mov ecx, count  ; number of 32-bit words to move
			mov eax, data   ; 32-bit data
			rep stosd       ; move data
	} // end asm

} // end Mem_Set_QUAD
#endif

int Open_Error_File(char *filename, FILE *fp_override)
{
	// this function creates the output error file

	// is user requesting special file handle? stdout, stderr, etc.?
	if (fp_override)
	{
		fp_error = fp_override;
	}
	else
	{
		// test if this file is valid
		if ((fp_error = fopen(filename,"w"))==NULL)
			return(0);
	}

	// get the current time
	struct _timeb timebuffer;
	char *timeline;
	char timestring[280];

	_ftime(&timebuffer);
	timeline = ctime(&(timebuffer.time));

	sprintf(timestring, "%.19s.%hu, %s", timeline, timebuffer.millitm, &timeline[20]);

	// write out error header with time
	Write_Error("\nOpening Error Output File (%s) on %s\n",filename,timestring);

	// now the file is created, re-open with append mode

	if (!fp_override)
	{
		fclose(fp_error);
		if ((fp_error = fopen(filename,"a+"))==NULL)
			return(0);
	}

	// return success
	return(1);

} // end Open_Error_File

///////////////////////////////////////////////////////////

int Close_Error_File(void)
{
	// this function closes the error file

	if (fp_error)
	{
		// write close file string
		Write_Error("\nClosing Error Output File.");

		if (fp_error!=stdout || fp_error!=stderr)
		{
			// close the file handle
			fclose(fp_error);
		} 

		fp_error = NULL;

		// return success
		return(1);
	} // end if
	else
		return(0);

} // end Close_Error_File

///////////////////////////////////////////////////////////

int Write_Error(char *string, ...)
{
	// this function prints out the error string to the error file

	char buffer[1024]; // working buffer

	va_list arglist; // variable argument list

	// make sure both the error file and string are valid
	if (!string || !fp_error)
		return(0);

	// print out the string using the variable number of arguments on stack
	va_start(arglist,string);
	vsprintf(buffer,string,arglist);
	va_end(arglist);

	// write string to file
	fprintf(fp_error,buffer);

	// flush buffer incase the system bails
	fflush(fp_error);

	// return success
	return(1);
} // end Write_Error

int Fast_Distance_2D(int x, int y)
{
	// this function computes the distance from 0,0 to x,y with 3.5% error

	// first compute the absolute value of x,y
	x = abs(x);
	y = abs(y);

	// compute the minimum of x,y
	int mn = MIN(x,y);

	// return the distance
	return(x+y-(mn>>1)-(mn>>2)+(mn>>4));

} // end Fast_Distance_2D

///////////////////////////////////////////////////////////////////////////////

float Fast_Distance_3D(float fx, float fy, float fz)
{
	// this function computes the distance from the origin to x,y,z

	int temp;  // used for swaping
	int x,y,z; // used for algorithm

	// make sure values are all positive
	x = fabs(fx) * 1024;
	y = fabs(fy) * 1024;
	z = fabs(fz) * 1024;

	// sort values
	if (y < x) SWAP(x,y,temp)

		if (z < y) SWAP(y,z,temp)

			if (y < x) SWAP(x,y,temp)

				int dist = (z + 11 * (y >> 5) + (x >> 2) );

	// compute distance with 8% error
	return((float)(dist >> 10));

} // end Fast_Distance_3D
void Build_Sin_Cos_Tables(void)
{

	// create sin/cos lookup table
	// note the creation of one extra element; 360
	// this helps with logic in using the tables

	// generate the tables 0 - 360 inclusive
	for (int ang = 0; ang <= 360; ang++)
	{
		// convert ang to radians
		float theta = (float)ang*PI/(float)180;

		// insert next entry into table
		cos_look[ang] = cos(theta);
		sin_look[ang] = sin(theta);

	} // end for ang

} // end Build_Sin_Cos_Tables

//////////////////////////////////////////////////////////////

int Translate_Polygon2D(POLYGON2D_PTR poly, int dx, int dy)
{
	// this function translates the center of a polygon

	// test for valid pointer
	if (!poly)
		return(0);

	// translate
	poly->x0+=dx;
	poly->y0+=dy;

	// return success
	return(1);

} // end Translate_Polygon2D

///////////////////////////////////////////////////////////////

int Rotate_Polygon2D(POLYGON2D_PTR poly, int theta)
{
	// this function rotates the local coordinates of the polygon

	// test for valid pointer
	if (!poly)
		return(0);

	// test for negative rotation angle
	if (theta < 0)
		theta+=360;

	// loop and rotate each point, very crude, no lookup!!!
	for (int curr_vert = 0; curr_vert < poly->num_verts; curr_vert++)
	{

		// perform rotation
		float xr = (float)poly->vlist[curr_vert].x*cos_look[theta] - 
			(float)poly->vlist[curr_vert].y*sin_look[theta];

		float yr = (float)poly->vlist[curr_vert].x*sin_look[theta] + 
			(float)poly->vlist[curr_vert].y*cos_look[theta];

		// store result back
		poly->vlist[curr_vert].x = xr;
		poly->vlist[curr_vert].y = yr;

	} // end for curr_vert

	// return success
	return(1);

} // end Rotate_Polygon2D

////////////////////////////////////////////////////////

int Scale_Polygon2D(POLYGON2D_PTR poly, float sx, float sy)
{
	// this function scalesthe local coordinates of the polygon

	// test for valid pointer
	if (!poly)
		return(0);

	// loop and scale each point
	for (int curr_vert = 0; curr_vert < poly->num_verts; curr_vert++)
	{
		// scale and store result back
		poly->vlist[curr_vert].x *= sx;
		poly->vlist[curr_vert].y *= sy;

	} // end for curr_vert

	// return success
	return(1);

} // end Scale_Polygon2D

///////////////////////////////////////////////////////////////
// these are the matrix versions, note they are more inefficient for
// single transforms, but their power comes into play when you concatenate
// multiple transformations, not to mention that all transforms are accomplished
// with the same code, just the matrix differs

int Translate_Polygon2D_Mat(POLYGON2D_PTR poly, int dx, int dy)
{
	// this function translates the center of a polygon by using a matrix multiply
	// on the the center point, this is incredibly inefficient, but for educational purposes
	// if we had an object that wasn't in local coordinates then it would make more sense to
	// use a matrix, but since the origin of the object is at x0,y0 then 2 lines of code can
	// translate, but lets do it the hard way just to see :)

	// test for valid pointer
	if (!poly)
		return(0);

	MATRIX3X2 mt; // used to hold translation transform matrix

	// initialize the matrix with translation values dx dy
	Mat_Init_3X2(&mt,1,0, 0,1, dx, dy); 

	// create a 1x2 matrix to do the transform
	MATRIX1X2 p0 = {poly->x0, poly->y0};
	MATRIX1X2 p1 = {0,0}; // this will hold result

	// now translate via a matrix multiply
	Mat_Mul_1X2_3X2(&p0, &mt, &p1);

	// now copy the result back into polygon
	poly->x0 = p1.M[0];
	poly->y0 = p1.M[1];

	// return success
	return(1);

} // end Translate_Polygon2D_Mat

///////////////////////////////////////////////////////////////

int Rotate_Polygon2D_Mat(POLYGON2D_PTR poly, int theta)
{
	// this function rotates the local coordinates of the polygon

	// test for valid pointer
	if (!poly)
		return(0);

	// test for negative rotation angle
	if (theta < 0)
		theta+=360;

	MATRIX3X2 mr; // used to hold rotation transform matrix

	// initialize the matrix with translation values dx dy
	Mat_Init_3X2(&mr,cos_look[theta],sin_look[theta], 
		-sin_look[theta],cos_look[theta], 
		0, 0); 

	// loop and rotate each point, very crude, no lookup!!!
	for (int curr_vert = 0; curr_vert < poly->num_verts; curr_vert++)
	{
		// create a 1x2 matrix to do the transform
		MATRIX1X2 p0 = {poly->vlist[curr_vert].x, poly->vlist[curr_vert].y};
		MATRIX1X2 p1 = {0,0}; // this will hold result

		// now rotate via a matrix multiply
		Mat_Mul_1X2_3X2(&p0, &mr, &p1);

		// now copy the result back into vertex
		poly->vlist[curr_vert].x = p1.M[0];
		poly->vlist[curr_vert].y = p1.M[1];

	} // end for curr_vert

	// return success
	return(1);

} // end Rotate_Polygon2D_Mat

////////////////////////////////////////////////////////

int Scale_Polygon2D_Mat(POLYGON2D_PTR poly, float sx, float sy)
{
	// this function scalesthe local coordinates of the polygon

	// test for valid pointer
	if (!poly)
		return(0);


	MATRIX3X2 ms; // used to hold scaling transform matrix

	// initialize the matrix with translation values dx dy
	Mat_Init_3X2(&ms,sx,0, 
		0,sy, 
		0, 0); 


	// loop and scale each point
	for (int curr_vert = 0; curr_vert < poly->num_verts; curr_vert++)
	{
		// scale and store result back

		// create a 1x2 matrix to do the transform
		MATRIX1X2 p0 = {poly->vlist[curr_vert].x, poly->vlist[curr_vert].y};
		MATRIX1X2 p1 = {0,0}; // this will hold result

		// now scale via a matrix multiply
		Mat_Mul_1X2_3X2(&p0, &ms, &p1);

		// now copy the result back into vertex
		poly->vlist[curr_vert].x = p1.M[0];
		poly->vlist[curr_vert].y = p1.M[1];

	} // end for curr_vert

	// return success
	return(1);

} // end Scale_Polygon2D_Mat

///////////////////////////////////////////////////////////

int Mat_Mul_3X3(MATRIX3X3_PTR ma, 
	MATRIX3X3_PTR mb,
	MATRIX3X3_PTR mprod)
{
	// this function multiplies two matrices together and 
	// and stores the result

	for (int row=0; row<3; row++)
	{
		for (int col=0; col<3; col++)
		{
			// compute dot product from row of ma 
			// and column of mb

			float sum = 0; // used to hold result

			for (int index=0; index<3; index++)
			{
				// add in next product pair
				sum+=(ma->M[row][index]*mb->M[index][col]);
			} // end for index

			// insert resulting row,col element
			mprod->M[row][col] = sum;

		} // end for col

	} // end for row

	return(1);

} // end Mat_Mul_3X3

////////////////////////////////////////////////////////////////

int Mat_Mul_1X3_3X3(MATRIX1X3_PTR ma, 
	MATRIX3X3_PTR mb,
	MATRIX1X3_PTR mprod)
{
	// this function multiplies a 1x3 matrix against a 
	// 3x3 matrix - ma*mb and stores the result

	for (int col=0; col<3; col++)
	{
		// compute dot product from row of ma 
		// and column of mb

		float sum = 0; // used to hold result

		for (int index=0; index<3; index++)
		{
			// add in next product pair
			sum+=(ma->M[index]*mb->M[index][col]);
		} // end for index

		// insert resulting col element
		mprod->M[col] = sum;

	} // end for col

	return(1);

} // end Mat_Mul_1X3_3X3

////////////////////////////////////////////////////////////////

int Mat_Mul_1X2_3X2(MATRIX1X2_PTR ma, 
	MATRIX3X2_PTR mb,
	MATRIX1X2_PTR mprod)
{
	// this function multiplies a 1x2 matrix against a 
	// 3x2 matrix - ma*mb and stores the result
	// using a dummy element for the 3rd element of the 1x2 
	// to make the matrix multiply valid i.e. 1x3 X 3x2

	for (int col=0; col<2; col++)
	{
		// compute dot product from row of ma 
		// and column of mb

		float sum = 0; // used to hold result

		for (int index=0; index<2; index++)
		{
			// add in next product pair
			sum+=(ma->M[index]*mb->M[index][col]);

			// add in last element * 1 
			sum+= mb->M[index][col];
		} // end for index

		// insert resulting col element
		mprod->M[col] = sum;

	} // end for col

	return(1);

} // end Mat_Mul_1X2_3X2

//////////////////////////////////////////////////////////////
#if 0
inline int Mat_Init_3X2(MATRIX3X2_PTR ma, 
	float m00, float m01,
	float m10, float m11,
	float m20, float m21)
{
	// this function fills a 3x2 matrix with the sent data in row major form
	ma->M[0][0] = m00; ma->M[0][1] = m01; 
	ma->M[1][0] = m10; ma->M[1][1] = m11; 
	ma->M[2][0] = m20; ma->M[2][1] = m21; 

	// return success
	return(1);

} // end Mat_Init_3X2
#endif
/////////////////////////////////////////////////////////////////